home *** CD-ROM | disk | FTP | other *** search
- program gifslow;
-
- {Written 1/16/88-1/19/88 by Jim Griebel. This software is experimental!
- USE AT YOUR OWN RISK. In the public domain. 'GIF' and 'Graphics Interchange
- Format' are trademarks of Compuserve, Inc., an H&R Block Company. 'Turbo
- Pascal' is a trademark of Borland International.}
-
- {This is a short simple GIF reader/displayer for the EGA, adapted from
- GIFREAD, an earlier effort targeted on the Hercules. No provision is made
- for saving files or for scrolling in this program, which is intended as an
- example. This is the ultraslow version, pure high level}
-
-
- uses crt,dos;
-
- type
-
- RasterArray = Array [0..63999] of byte;
- RasterP = ^RasterArray;
-
- var
- GifFile:File of RasterArray; {The input file}
- GifStuff:RasterP; {The heap array to hold it, raw}
- Raster:RasterP; {The raster data stream, unblocked}
- Raster2:RasterP; {More raster data stream if needed}
- Regs:Registers; {Turbo's predefined record}
-
- Byteoffset, {Computed byte position in RASTER array}
- Bitoffset {Bit offset of next code in RASTER array}
- :LongInt;
-
- Width, {Read from GIF header, image width}
- Height, { ditto, image height}
- LeftOfs, { ditto, image offset from left}
- TopOfs, { ditto, image offset from top}
- RWidth, { ditto, raster width}
- RHeight, { ditto, raster height}
- ClearCode, {GIF clear code}
- EOFCode, {GIF end-of-information code}
- OutCount, {Decompressor output 'stack count'}
- MaxCode, {Decompressor limiting value for current code size}
- Code, {Value returned by ReadCode}
- CurCode, {Decompressor variable}
- OldCode, {Decompressor variable}
- InCode, {Decompressor variable}
- FirstFree, {First free code, generated per GIF spec}
- FreeCode, {Decompressor, next free slot in hash table}
- GIFPtr, {Array pointers used during file read}
- RasterPtr,
- XC,YC, {Screen X and Y coords of current pixel}
- Pindex, {Index into screen save array}
- ReadMask, {Code AND mask for current code size}
- I {Loop counter, what else?}
- :word;
-
-
- Interlace, {True if interlaced image}
- NextRaster, {True if file > 64000 bytes}
- ColorMap {True if colormap present}
- :Boolean;
-
- ch {Utility}
- :char;
-
- a, {Utility}
- Resolution, {Resolution, read from GIF header}
- BitsPerPixel, {Bits per pixel, read from GIF header}
- Background, {Background color, read from GIF header}
- ColorMapSize, {Length of color map, from GIF header}
- CodeSize, {Code size, read from GIF header}
- InitCodeSize, {Starting code size, used during Clear}
- FinChar, {Decompressor variable}
- Pass, {Used by video output if interlaced pic}
- BitMask, {AND mask for data size}
- R,G,B
- :byte;
-
-
- {The hash table used by the decompressor}
- Prefix: Array [0..4095] of word;
- Suffix: Array [0..4095] of byte;
-
- {An output array used by the decompressor}
- Outcode:Array [0..1024] of byte;
-
- {The color map, read from the GIF header}
- Red,Green,Blue: array [0..255] of byte;
-
- {The EGA palette, derived from the color map}
- Palette: Array [0..255] of byte;
-
- {Strings to hold the filenames}
- FileString:String [80];
-
-
- Const
-
- MaxCodes: Array [0..9] of Word = (4,8,16,$20,$40,$80,$100,$200,$400,$800);
-
- CodeMask:Array [1..4] of byte= (1,3,7,15);
-
- PowersOf2: Array [0..8] of word=(1,2,4,8,16,32,64,128,256);
-
- Masks: Array [0..9] of Integer = (7,15,$1f,$3f,$7f,$ff,$1ff,$3ff,$7ff,$fff);
-
- Rastersize:Word = 64000;
-
-
- {This procedure checks to be sure we've got enough heap for the array
- we're trying to allocate, then allocates same. If there isn't enough
- heap available, we exit with an error}
-
- Procedure AllocMem (Var P:RasterP);
-
- Var ASize:Longint;
-
- Begin
- ASize:=MaxAvail;
- If ASize<RasterSize then
- Begin
- Textmode (15);
- Writeln ('Insufficient memory available!');
- Halt;
- End
- Else
- Getmem (P,RasterSize);
- End;
-
-
- {Mimics a file read of a single byte, reading from the input record rather
- than the file itself. If you wish to change back to a file of byte rather
- than using the faster read of the record, you can modify this routine to
- read directly from the file. This is simpler but slower}
-
- Function Getbyte:Byte;
-
- Begin
- If GIFPtr=RasterSize then Exit;
- Getbyte:=GIFStuff^[GIFPtr];
- GIFPtr:=Succ(GIFPtr);
- End;
-
- {Reads two bytes, to get a word value}
-
- Function Getword:Word;
-
- Var A,B:Byte;
-
- Begin
- A:=Getbyte;
- B:=Getbyte;
- Getword:=A+(256*B);
- End;
-
-
-
- {Mimic reading in the raster data. Unblock it into a single large array
- to save having to do this as we go, which makes life a lot simpler for
- the rest of the program. We cope here with files larger than 64000 bytes by
- doing another read from the input file, and by creating a second RASTER
- array if necessary to hold the excess unblocked data}
-
- Procedure ReadRaster;
-
- Var BlockLength:Byte;
- I,IOR:Integer;
-
- Begin
- RasterPtr:=0;
- Repeat
- BlockLength:=Getbyte;
- For I:=0 to Blocklength-1 do
- Begin
- If Gifptr = RasterSize then
- Begin
- {$I-}
- Read (GIFFile,GIFStuff^);
- {$I+}
- IOR:=IOResult;
- GIFPtr:=0;
- End;
- If not Nextraster then
- Raster^[RasterPtr]:=Getbyte else
- Raster2^[RasterPtr]:=Getbyte;
- RasterPtr:=Succ (RasterPtr);
- If RasterPtr=RasterSize then
- Begin
- NextRaster:=True;
- Rasterptr:=0;
- AllocMem (Raster2);
- End;
- End;
- Until Blocklength=0;
- End;
-
-
- {Fetch the next code from the raster data stream. The codes can be any
- length from 3 to 12 bits, packed into 8-bit bytes, so we have to maintain
- our location in the Raster array as a BIT offset. We compute the byte offset
- into the raster array by dividing this by 8, pick up three bytes, compute
- the bit offset into our 24-bit chunk, shift to bring the desired code to
- the bottom, then mask it off and return it. If the unblocked raster data
- overflows the original RASTER array, we switch to the second one}
-
- Procedure ReadCode;
-
- Var RawCode:LongInt;
- A,B:Word;
-
-
- Begin
- ByteOffset:=BitOffset div 8;
-
- {Pick up our 24-bit chunk}
-
- A:=Raster^[Byteoffset]+(256*Raster^[ByteOffset+1]);
- If CodeSize>=8 then
- Begin
- B:=Raster^[Byteoffset+2];
- RawCode:=A+(65536*B);
- End
- Else Rawcode:=A;
-
- {Doing the above calculation as a single statement, i.e.
- Rawcode:=Raster^[Byteoffset]+(256*Raster^[Byteoffset+1])+
- (65536*Raster[Byteoffset+2])
- sometimes returns incorrect results. This may or may not be a bug.}
-
-
- RawCode:=RawCode shr (BitOffset mod 8);
- Code:=RawCode and ReadMask;
-
- {Cope with overflow of the first RASTER array}
-
- If (Nextraster) and (Byteoffset>=63000) then
- Begin
- Move (Raster^[Byteoffset],Raster^[0],RasterSize-Byteoffset);
- Move (Raster2^[0],Raster^[RasterSize-Byteoffset],63000);
- Bitoffset:=Bitoffset mod 8;
- FreeMem (Raster2,RasterSize);
- End;
-
- BitOffset:=BitOffset+CodeSize;
-
- End;
-
-
- Procedure AddToPixel (Index:Byte);
-
-
- Begin
-
- Regs.AH:=12;
- Regs.AL:=Index;
- Regs.CX:=XC;
- Regs.DX:=YC;
- Intr ($10,Regs);
-
- {Update the X-coordinate, and if it overflows, update the Y-coordinate}
-
- XC:=Succ (XC);
- If XC=Width then
-
- {If a non-interlaced picture, just increment YC to the next scan line. If
- it's interlaced, deal with the interlace as described in the GIF spec. Put
- the decoded scan line out to the screen if we haven't gone past the bottom
- of it}
-
- Begin
-
- XC:=0;
- If not Interlace then YC:=Succ (YC) else
- Begin
- Case Pass of
- 0: Begin
- YC:=YC+8;
- If YC>=Height then
- Begin
- Pass:=Succ(Pass);
- YC:=4;
- End;
- End;
- 1: Begin
- YC:=YC+8;
- If YC>=Height then
- Begin
- Pass:=Succ(Pass);
- YC:=2;
- End;
- End;
- 2: Begin
- YC:=YC+4;
- If YC>=Height then
- Begin
- Pass:=Succ(Pass);
- YC:=1;
- End;
- End;
- 3: Begin
- YC:=YC+2;
- End;
- End; {Case}
- End; {If interlace}
- End;
-
- End;
-
- {Use the BIOS functions to set up the EGA. This avoids dependence on Turbo's
- GRAPH package and the necessity to keep .BGI files with the executable}
-
- Procedure InitEGA;
-
- Begin
-
-
- {Set EGA graphics mode}
-
- Regs.AX:=$0010;
- Intr ($10,Regs);
-
- {Set the palette}
-
- Regs.AX:=$1002;
- Regs.DX:=Ofs (Palette);
- Regs.ES:=Seg (Palette);
- Intr ($10,Regs);
-
- End;
-
-
- {Determine the palette value corresponding to the GIF colormap intensity
- value.}
-
- Procedure DetColor (Var PValue:Byte;MapValue:Byte);
-
- Var Local:Byte;
-
- Begin
- PValue:=MapValue div 64;
- If PValue=1 then PValue:=2 else
- If PValue=2 then PValue:=1;
- End;
-
- {Set the key variables to
- their necessary initial values.}
-
- Procedure ReInitialize;
- Begin
- XC:=0; {X and Y screen coords back to home}
- YC:=0;
- Pass:=0; {Interlace pass counter back to 0}
- Bitoffset:=0; {Point to the start of the raster data stream}
- GIFPtr:=0; {Mock file read pointer back to 0}
- End;
-
- {React to GIF clear code, or reset GIF decompression values back to their
- initial state when restarting.}
-
- Procedure DoClear;
-
- Begin
- CodeSize:=InitCodeSize;
- MaxCode:=MaxCodes [CodeSize-2];
- FreeCode:=FirstFree;
- ReadMask:=Masks [CodeSize-3];
- End;
-
- Begin {the main program}
-
- {Initialize a bunch of variables}
-
- ReInitialize; {Initialize common vars}
- Nextraster:=False; {Over 64000 flag off}
-
- {Get memory for the raster data array, and the input file data array}
-
- AllocMem (Raster);
- AllocMem (GIFStuff);
-
- {Prompt the user for the filename}
-
- Write ('Filename: ');
- Readln (Filestring);
-
-
- {Open the file}
-
- {$I-}
- Assign (giffile,FileString);
- Reset (giffile);
- {$I+}
-
- {Cope with I/O error should one occur}
-
- I:=IOResult;
- If I<>0 then
- Begin
- Writeln ('Error opening file ',FileString,'. Press any key ');
- Readln;
- Exit;
- End;
-
- {Read in the GIF file. Reading it as one big hunk rather than N bytes results
- in far faster disk I/O; see user notes. Error checking is turned off in
- order to avoid 'attempt to read past EOF' errors. If the file does not exist,
- this will be detected at RESET}
-
- Writeln ('Reading . . . ');
- {$I-}
- Read (GIFFile,GIFStuff^);
- {$I+}
-
- {Note that 4.0 requires this assignment, or else if an error results (as it
- will if the file is smaller than 64000 bytes) no I/O will be allowed for
- the remainder of the run}
-
- I:=IOResult;
-
- {Deal with the GIF header. Start by checking the GIF tag to make sure this
- is a GIF file}
-
- FileString:='';
- for i:=1 to 6 do
- Begin
- FileString:=FileString+chr(Getbyte);
- End;
- If FileString<>'GIF87a' then
- Begin
- Writeln ('Not a GIF file, or header read error. Press any key ');
- Readln;
- Exit;
- End;
-
- {Get variables from the GIF screen descriptor}
-
- RWidth:=Getword; {The raster width and height}
- RHeight:=Getword;
- {Get the packed byte immediately following and decode it}
- B:=Getbyte;
- If B and $80=$80 then Colormap:=True else Colormap:=False;
- Resolution:=B and $70 shr 5 +1;
- BitsPerPixel:=B and 7 +1;
- If BitsPerPixel=1 then I:=2 else I:=1 shl BitsPerPixel;
- Write ('Colors: ',I);
- BitMask:=CodeMask [BitsPerPixel];
- Background:=Getbyte;
- B:=Getbyte; {Skip byte of 0's}
-
- {Compute size of colormap, and read in the global one if there. Compute
- values to be used when we set up the EGA palette}
-
- ColorMapSize:=1 shl BitsPerPixel;
- If Colormap then
- Begin
- For I:=0 to ColorMapSize-1 do
- Begin
- Red [I]:=Getbyte;
- Green [I]:=Getbyte;
- Blue [I]:=Getbyte;
- DetColor (R,Red[I]);
- DetColor (G,Green [I]);
- DetColor (B,Blue [I]);
- Palette [I]:=B and 1+(2*(G and 1))+(4*(R and 1))+(8*(B div 2))+(16*(G div 2))+(32*(R div 2));
- End;
- Writeln;
- Palette [16]:=Background;
- End;
-
- {Now read in values from the image descriptor}
-
- B:=Getbyte; {skip image seperator}
- Leftofs:=Getword;
- Topofs:=Getword;
- Width:=Getword;
- Writeln ('Width: ',Width);
- Height:=Getword;
- Writeln ('Height: ',Height);
- A:=Getbyte;
- If A and $40=$40 then Interlace:=True else Interlace:=False;
-
-
- {Note that we ignore the possible existence of a local color map. I've yet
- to encounter an image that had one, and the spec says it's defined for
- future use. This could lead to an error reading some files}
-
- {Start reading the raster data. First we get the intial code size}
-
- Codesize:=Getbyte;
-
- {Compute decompressor constant values, based on the code size}
-
- ClearCode:=PowersOf2 [Codesize];
- EOFCode:=ClearCode+1;
- FirstFree:=ClearCode+2;
- FreeCode:=FirstFree;
-
- {The GIF spec has it that the code size is the code size used to compute the
- above values is the code size given in the file, but the code size used in
- compression/decompression is the code size given in the file plus one.}
-
- Codesize:=Succ (Codesize);
- InitCodeSize:=Codesize;
- Maxcode:=Maxcodes [Codesize-2];
- ReadMask:=Masks [Codesize-3];
-
- {Read the raster data. Here we just transpose it from the GIF array to the
- Raster array, turning it from a series of blocks into one long data stream,
- which makes life much easier for ReadCode}
-
- Writeln ('Unblocking');
- ReadRaster;
-
- {Get ready to do the actual read/display. Free up the heap used by the
- GIF array since we don't need it any more, and if the user wants to save,
- reclaim it for the Picture array}
-
- FreeMem (GIFStuff,RasterSize);
- OutCount:=0;
-
- {Set up the EGA}
-
- InitEGA;
-
- {Decompress the file, continuing until you see the GIF EOF code. One
- obvious enhancement is to add checking for corrupt files here.}
-
- Repeat
-
- {Get the next code from the raster array}
-
- ReadCode;
-
- If Code <> EOFCode then
- Begin
-
- {Clear code sets everything back to its initial value, then reads
- the immediately subsequent code as uncompressed data.}
-
- If Code = ClearCode then
- Begin
- DoClear;
- ReadCode;
- CurCode:=Code;
- OldCode:=Code;
- FinChar:=Code and BitMask;
- AddToPixel (FinChar);
- End
- Else
-
- {If not a clear code, then must be data: save same as CurCode and InCode}
-
- Begin
- CurCode:=Code;
- InCode:=Code;
-
- {If greater or equal to FreeCode, not in the hash table yet; repeat
- the last character decoded}
-
- If Code>=FreeCode then
- Begin
- CurCode:=OldCode;
- OutCode [OutCount]:=FinChar;
- OutCount:=Succ (OutCount);
- End;
-
- {Unless this code is raw data, pursue the chain pointed to by CurCode
- through the hash table to its end; each code in the chain puts its
- associated output code on the output queue.}
-
- If CurCode>BitMask then
- Repeat
- OutCode [OutCount]:=Suffix [CurCode];
- OutCount:=Succ (OutCount);
- CurCode:=Prefix [CurCode];
- Until CurCode<=BitMask;
-
- {The last code in the chain is treated as raw data.}
-
- FinChar:=CurCode and BitMask;
- OutCode [OutCount]:=FinChar;
- OutCount:=Succ (OutCount);
-
- {Now we put the data out to the using routine. It's been stacked
- LIFO, so deal with it that way}
-
- For I:=OutCount-1 downto 0 do
- AddToPixel (Outcode [I]);
-
- {Make darned sure OutCount gets set back to start}
-
- OutCount:=0;
-
- {Build the hash table on-the-fly. No table is stored in the file.}
-
- Prefix [FreeCode]:=OldCode;
- Suffix [FreeCode]:=FinChar;
- OldCode:=InCode;
-
- {Point to the next slot in the table. If we exceed the current MaxCode
- value, increment the code size unless it's already 12. If it is, do
- nothing: the next code decompressed better be CLEAR}
-
- FreeCode:=Succ (FreeCode);
- If FreeCode>=MaxCode then
- Begin
- If CodeSize < 12 then
- Begin
- CodeSize:=Succ (CodeSize);
- MaxCode:=MaxCode*2;
- ReadMask:=Masks [CodeSize-3];
- End;
- End;
- End {not Clear};
-
- If Keypressed then
- Begin
- Ch:=Readkey;
- If Ch=#27 then
- Begin
- Textmode (15);
- Exit;
- End;
- End;
- End; {not EOFCode}
- Until Code=EOFCode;
-
- Writeln (^G); {signals whole picture decoded}
-
- {Read one key, then pack it in}
-
- Ch:=Readkey;
-
- Textmode (15); {Back to text}
- Close (GifFile);
- FreeMem (Raster,RasterSize);
-
- End.
- -------- end of PASCAL Source file (file name is GIFSLOW.PAS) --------
- -------- start of User Notes (file name is USER.NOT) --------
- WHAT YOU GET
-
- The .ARC file you got should contain the following files:
-
- This USER.NOT file
- GIFSLOW.PAS
- GIFEGA.PAS
- NLZW.ASM
- READRAST.ASM
- SCROLL.ASM
- SCRSAVE.ASM
- GIFSLOW.EXE
- GIFEGA.EXE
-
- These make up the source and executable for two GIF reader/displayers
- written in Turbo Pascal 4.0 and MASM compatible assembly language. GIFSLOW
- is written entirely in Turbo to lay out the techniques being used in a form
- more accessible than assembly language, and boy, does it live up to its
- name -- you can go have a short beer while the program is decoding and
- displaying a single picture. GIFEGA is much faster -- it takes about 15-30
- seconds to display a picture depending on its complexity -- and adds a
- directory display, scrolling, and a file save feature. The guts of GIFEGA
- are in assembly-language external files, which I've tuned to make it as
- fast as I can manage. The principal reason for putting up these amateur
- works is to provide source code for LZW decompression, which seems to be a
- stumbling block for many who'd otherwise be writing their own GIF readers.
- Using the program is pretty straightforward. With GIFSLOW all you do
- is type in the filename at the prompt. If you get tired of waiting for the
- picture to fully display, hitting ESC will get you out of it. GIFEGA is a
- little more sophisticated. The program will display a list of eligible
- files (files with the extension '.GIF') in the current directory. You're
- then prompted for a filename. Just hitting ENTER at this point gets you out
- of the program. Type in a single '\' character and the program will prompt
- you for a new path name, then switch to that new path and display a new
- list of files. Type in the filename you'd like to see displayed (you don't
- have to add the .GIF extension), and the program will display it. When the
- display is done, the computer will beep. If the picture is larger
- vertically than the screen, you can then scroll it by using the Up and Down
- Arrows and the Home and End keys. The Up and Down arrow keys move the
- picture in increments of ten scan lines at a time. The Home key moves to
- the top of the picture, and the End key moves to the bottom. Hitting ESC
- gets you out of the display and produces a prompt asking you to hit the
- space bar if you want to save the file as an EGA Paint compatible .SCR
- file. Hit the space bar and you'll save the file with the same pathname and
- filename but the extension .SCR. That's all there is to it.
-
- HOW THE PROGRAM WORKS
-
- While the operation of the program is pretty fully explained in the
- comments, a brief sketch of its operation and a few notes on the techniques
- used are probably useful. The program starts by getting and sorting the
- directory list, displaying it, and then getting the filename from the user.
- The file is then read in, and it's worth spending a few moments on how we
- do this.
- Since a GIF file has a header of no fixed length, the obvious way to
- treat it is as a file of byte. The problem is that reading long files a
- byte at a time in Turbo is a very slow process; if we cheat the compiler a
- bit by claiming that the file is really a single large data structure, and
- try to read it all in one fell swoop, we can speed matters up enormously.
- This is what both programs do. The file is declared as a RasterArray,
- a one-dimensioned array of 64,000 bytes, which is maintained on the heap.
- We try to read this big data structure from the file. This demands that we
- turn off I/O checking during the read (otherwise if the file is smaller
- than 64,000 bytes an error will occur) and call IORESULT after the read (or
- else Turbo will refuse to do any more I/O). If the file is larger than
- 64,000 bytes, this will be detected and dealt with later.
- Now we have the whole file, or most of it, in memory, and we have to
- read the header. This is done by the functions GetByte and GetWord, which
- mimic a file read of a single byte or of a word. We read the necessary
- variables described in the GIF spec from the file, and compute some more
- based on those. As described in 'Problems and Limitations' below we don't
- cope with absolutely every possibility outlined in the GIF spec, but I've
- yet to have the program fail on this account (although making the program
- conform completely to the spec is one obvious enhancement). We also read
- and set up the color palette. At this point, if it's a 256-color picture,
- we do an extremely crude fix to make the picture display in 16 colors with
- (hopefully) some remote resemblance to the original. This is done by
- arbitrarily declaring the first 16 colors in the palette to be the EGA
- palette, then resetting the color values above 16 to their nearest
- equivalents below 16. This could be improved on, and I'm open to
- suggestions as to how.
- Once we have the necessary variables and constants all set up, we're
- ready to start decompressing. The first step in this is to unblock the
- whole data stream -- that is, to turn the data stream from a series of 255-
- byte or less blocks into one long stream of data. This may seem to be an
- unnecessary step; a lot of GIF readers seem to unblock the data stream as
- they go, but if we do that then each and every time we read a code we have
- to check to see if we're near the end of the current block, and if so, move
- the tail end of that block up to the front of the read buffer, unblock and
- add on the next block, and recompute BITOFFSET to point back to the start
- of the buffer. This makes for an awful lot of checking and computation
- repeated God knows how many times. Unblocking the data stream beforehand
- makes the READCODE routine much simpler, whether in high level or in
- assembly, and faster as well. (And why Compuserve decided to block the durn
- thing beats me: tradition?)
- During unblocking we also cope with files larger than 64,000 bytes.
- The logic of this is simple enough: if we run out of GIFSTUFF during
- unblocking, we allocate memory for another RASTER array, do another read
- from the file, and put the rest of the data stream into this second RASTER
- array. During decoding, if this second array exists, we check to see if
- we're near the end of the first one. If so, we move the end of the first
- one up to the front of it, copy the contents of the second RASTER array
- into the first, reset BITOFFSET back to the start of the array, and keep
- on. This is just what other readers do on a block-by-block basis, but done
- on a larger scale, and only once. You can see this happen on a file bigger
- than 64,000 bytes: there'll be a 'hiccup' when the arrays are readjusted.
- This is faster than waiting and doing a second file read during the actual
- decompress/display, but as a consequence the program uses a lot of memory:
- as much as 192K over and above the size of the program at peak.
- Once we have our data stream unblocked we can unravel the LZW
- decompression. This, frankly, I don't understand too well, but what comes
- out of the decompression cycle are pixels ready to write to the EGA screen.
- GIFSLOW does this a pixel at a time using a BIOS call; GIFEGA does this a
- scan line at a time, writing directly to EGA memory. We take advantage of
- the fact that the EGA's memory will hold a 640x480 picture (even if the
- card will only display 640x350) to get the whole picture into EGA memory,
- so that when we're done we can grab the four bit planes and save them in
- new heap arrays. We can then use those heap arrays to scroll the picture or
- save it as an EGA Paint-compatible .SCR file (which consists of the header
- followed by the four bit planes in order 0,2,1,3). Scrolling is done only
- if the picture is larger than the screen, and is done by using Write Mode 0
- to move the bitplanes from the heap arrays back into the EGA memory,
- starting at an an offset into the bitplane arrays determined by the Turbo
- variable ROLL. Originally the scrolling routine moved the bitplanes one at
- a time and all at a time, but this led to a 'rainbow' effect with some
- pictures (you could see the bitplanes being moved) and the routine was
- changed to move a scan line from each bitplane at a time, which got rid of
- the problem but allows you to see the scroll in action as the picture
- ripples down the screen. This could doubtless be improved.
-
- LIMITATIONS AND KNOWN PROBLEMS
-
- The program deviates somewhat from the letter of the GIF spec, as
- follows:
- No provision is made for a default colormap if a global one is not
- supplied.
- No provision is made for dealing with a local colormap.
- No provision is made for dealing with GIF enhancements, nor with
- multiple images in the same file.
- While all these are deviations from the strict spec, I've yet to have
- the program fail on that account.
- The program doesn't cope well with 256-color images. This is doubtless
- fixable (and one obvious area for improvement) but I haven't spent much
- time on it.
- No provision is made in the program for modes other than vanilla EGA
- (640x350x16). One consequence of this is that though the save routine can
- write .SCP (640x480) files as well as .SCR files, this feature is
- permanently turned off in the program as it stands. This can be fixed, if
- you have an EGA that will handle more resolution, by changing the typed
- constant EGAHeight to 480.
- The program doesn't work with GRABEGA; the files produced seem to have
- the wrong palette setting. Since GRABEGA works with other GIF readers the
- bug would seem to be in GIFEGA, but I don't know where, and since the
- program incorporates a .SCR save routine I'm not looking too hard.
-
- Some obvious areas of enhancement would include: more comprehensive
- error checking and recovery (at present the program will lock up or
- otherwise die if there's an error in the GIF data stream; detecting these
- is tough), printing, a sexier directory display with moving highlights,
- more save file formats, etc. More professional programmers would doubtless
- like more modularity and structure, less reliance on globals, and so on, in
- the code; anyone is welcome to dive in and improve the program any way he
- or she can.
-
- I hope you enjoy the program; I had fun writing it, and I hope you
- have fun with it. The code is placed in the public domain, since it's based
- on the work of others who were generous enough to place their code in the
- public domain; chief among these is Tom Pfau of DEC, who wrote the public
- domain file unsqueezer LZDCMP, on which this program is based. While I've
- claimed no copyright and you can do what you like with the code, it's
- intended for the use of individuals for their own enjoyment, and I ask that
- you don't use it in any program transferred for a fee.
-
- -------- end of User Notes (file name is USER.NOT) --------
-
-